package com.ymx.darling.wrapper.fragment;

import com.ymx.darling.bean.sql.BoundSql;
import com.ymx.darling.bean.sql.SqlParsingHelper;
import com.ymx.darling.util.ArrayUtil;
import com.ymx.darling.util.LambdaUtil;
import com.ymx.darling.util.StringUtil;
import com.ymx.darling.wrapper.fragment.lambda.LambdaGetter;
import com.ymx.darling.wrapper.fragment.lambda.WhereLambda;
import com.ymx.darling.bean.sql.SqlStatement;

import java.util.*;

/**
 * where语句构建器
 * @author 爱Jav爱的小于
 */
public class WhereSegment implements SqlSegment {
    private final LinkedList<WhereCondition> whereConditions;

    public WhereSegment(){
        this.whereConditions = new LinkedList<>();
    }

    @Override
    public SqlStatement getSqlStatement() {
        StringBuilder conditionBuilder = new StringBuilder();
        List<Object> params = new ArrayList<>();
        String previousCondition = null;

        for (WhereCondition condition : this.whereConditions) {
            // 判断是否自动添加and条件
            if (StringUtil.isNoNull(previousCondition) &&
                    this.isNotLogicalOperator(previousCondition) &&
                    this.isNotLogicalOperator(condition.getCondition())) {
                conditionBuilder.append(" ").append(SqlOperator.AND.getValue());
            }
            Object param = condition.getParam();
            conditionBuilder.append(" ").append(condition.getCondition());
            // 过滤无参的Condition
            if (condition.isParam()) {
                if (param instanceof List) {
                    params.addAll((List<?>) param);
                } else {
                    params.add(param);
                }
            }
            previousCondition = condition.getCondition();
        }

        return new SqlStatement(conditionBuilder.toString().trim(), params);
    }


    /**
     * 判断不是逻辑运算符
     * @param condition condition
     * @return boolean
     */
    private boolean isNotLogicalOperator(String condition) {
        String and = SqlOperator.AND.getValue();
        String or = SqlOperator.OR.getValue();

        return !(and.equals(condition) || or.equals(condition));
    }


    /**
     * 拼接二元表达式操作
     * 如 > < == !=
     * @param one 字段名称
     * @param value 字段值
     * @param operator 运算符号
     */
    private void compareSplit(String one, Object value, SqlOperator operator){
        String condition = String.format("%s %s ?", one, operator.getValue());
        this.whereConditions.add(new WhereCondition(condition, value));
    }


    /**
     * 拼接sql的二元操作
     * @param colum 字段名称
     * @param operator 运算类型
     * @param sql sql语句
     */
    private void splitCompareSql(String colum, SqlOperator operator, String sql, Object... inSqlParam){
        BoundSql boundSql = SqlParsingHelper.parseSqlTemplate(sql, inSqlParam);
        boundSql.bindSqlParameter();
        String condition = String.format("%s %s %s", colum, operator.getValue(), boundSql.getSql());
        this.whereConditions.add(new WhereCondition(condition, boundSql.getBindParameters()));
    }


    /**
     * 拼接范围性操作
     * 例如 colum in (2,3,5,8,6)
     * @param colum 字段名
     * @param operator 运算符
     * @param values 值
     */
    private void splitInValues(String colum, SqlOperator operator, List<?> values){
        String joinContext = String.join(", ", Collections.nCopies(values.size(), "?"));
        String condition = String.format("%s %s (%s)", colum, operator.getValue(), joinContext);
        this.whereConditions.add(new WhereCondition(condition, values));
    }


    /**
     * 拼接括号内容
     * @param lambda 括号内的表达式
     * 如 (name = ?) 或 and(name = ?) 或 or(name = ?)
     */
    private void splitBracket(WhereLambda lambda){
        //获取括号内的where表达式
        WhereSegment inBracket = lambda.subWhere(new WhereSegment());
        //返回括号内表达式的SQL字符串和参数数组
        SqlStatement bracketSqlStatement = inBracket.getSqlStatement();
        //将括号内SQL字符串放入当前主SQL字符串中
        String condition = "(" + bracketSqlStatement.getSql() + ")";
        this.whereConditions.add(new WhereCondition(condition, bracketSqlStatement.getParamList()));
    }


    /**
     * 拼接字段之间的表达式 比如 字段1 = 字段2  或者 字段1 > 字段2
     * @param col1 第一个字段
     * @param col2 第二个字段
     * @param operator 比较的运算付 > < = != >= <=
     */
    private void splitColumnExpression(String col1, String col2, SqlOperator operator){
        String condition = String.format("%s %s %s", col1, operator.getValue(), col2);
        this.whereConditions.add(new WhereCondition(condition));
    }


    /**
     * 拼接Exists语句
     * @param operator exists 或者 not exists
     * @param sql sql
     * @param param param
     */
    private void splitExists(SqlOperator operator, String sql, Object... param) {
        String condition = String.format("%s (%s)", operator.getValue(), sql);
        this.whereConditions.add(new WhereCondition(condition, ArrayUtil.asList(param)));
    }


    private void splitBetween(String column, SqlOperator operator, Object one, Object two) {
        String condition = String.format("%s %s ? AND ?", column, operator.getValue());
        this.whereConditions.add(new WhereCondition(condition, ArrayUtil.asList(one, two)));
    }


    public void splitLike(String column, SqlOperator operator, String param) {
        if (StringUtil.isNoNull(param)) {
            String condition;
            switch (operator) {
                case LIKE:
                    condition = String.format("%s LIKE '%s'", column, param);
                    break;
                case LIKE_LEFT:
                    condition = String.format("%s LIKE '%%%s'", column, param);
                    break;
                case LIKE_RIGHT:
                    condition = String.format("%s LIKE '%s%%'", column, param);
                    break;
                case NOT_LIKE:
                    condition = String.format("%s NOT LIKE '%s'", column, param);
                    break;
                case NOT_LIKE_LEFT:
                    condition = String.format("%s NOT LIKE '%%%s'", column, param);
                    break;
                default:
                    condition = String.format("%s NOT LIKE '%s%%'", column, param);
                    break;
            }
            this.whereConditions.add(new WhereCondition(condition));
        }
    }


    public WhereSegment whereSegment(String sqlSnippet, Object... param){
        BoundSql boundSql = SqlParsingHelper.parseSqlTemplate(sqlSnippet, param);
        boundSql.bindSqlParameter();
        this.whereConditions.add(new WhereCondition(boundSql.getSql(), boundSql.getBindParameters()));

        return this;
    }

    public WhereSegment columnEq(String col1, String col2){
        this.splitColumnExpression(col1, col2, SqlOperator.EQ);
        return this;
    }

    public <L1,L2> WhereSegment columnEq(LambdaGetter<L1> table1Colum, LambdaGetter<L2> table2Colum){
        String t1ColumnName = LambdaUtil.getColumnName(table1Colum);
        String t2ColumnName = LambdaUtil.getColumnName(table2Colum);
        return this.columnEq(t1ColumnName, t2ColumnName);
    }

    public WhereSegment columnNe(String col1, String col2){
        this.splitColumnExpression(col1, col2, SqlOperator.NE);
        return this;
    }

    public <L1,L2> WhereSegment columnNe(LambdaGetter<L1> table1Colum, LambdaGetter<L2> table2Colum){
        String t1ColumnName = LambdaUtil.getColumnName(table1Colum);
        String t2ColumnName = LambdaUtil.getColumnName(table2Colum);
        return this.columnNe(t1ColumnName, t2ColumnName);
    }

    public WhereSegment columnGt(String col1, String col2){
        this.splitColumnExpression(col1, col2, SqlOperator.GT);
        return this;
    }

    public <L1,L2> WhereSegment columnGt(LambdaGetter<L1> table1Colum, LambdaGetter<L2> table2Colum){
        String t1ColumnName = LambdaUtil.getColumnName(table1Colum);
        String t2ColumnName = LambdaUtil.getColumnName(table2Colum);
        return this.columnGt(t1ColumnName, t2ColumnName);
    }

    public WhereSegment columnGe(String col1, String col2){
        this.splitColumnExpression(col1, col2, SqlOperator.GE);
        return this;
    }

    public <L1,L2> WhereSegment columnGe(LambdaGetter<L1> table1Colum, LambdaGetter<L2> table2Colum){
        String t1ColumnName = LambdaUtil.getColumnName(table1Colum);
        String t2ColumnName = LambdaUtil.getColumnName(table2Colum);
        return this.columnGe(t1ColumnName, t2ColumnName);
    }

    public WhereSegment columnLt(String col1, String col2){
        this.splitColumnExpression(col1, col2, SqlOperator.LT);
        return this;
    }

    public <L1,L2> WhereSegment columnLt(LambdaGetter<L1> table1Colum, LambdaGetter<L2> table2Colum){
        String t1ColumnName = LambdaUtil.getColumnName(table1Colum);
        String t2ColumnName = LambdaUtil.getColumnName(table2Colum);
        return this.columnLt(t1ColumnName, t2ColumnName);
    }

    public WhereSegment columnLe(String col1, String col2){
        this.splitColumnExpression(col1, col2, SqlOperator.LE);
        return this;
    }

    public <L1,L2> WhereSegment columnLe(LambdaGetter<L1> table1Colum, LambdaGetter<L2> table2Colum){
        String t1ColumnName = LambdaUtil.getColumnName(table1Colum);
        String t2ColumnName = LambdaUtil.getColumnName(table2Colum);
        return this.columnLe(t1ColumnName, t2ColumnName);
    }

    public WhereSegment bracket(WhereLambda lambda){
        this.splitBracket(lambda);
        return this;
    }

    public WhereSegment eq(String col, Object value){
        this.compareSplit(col, value, SqlOperator.EQ);
        return this;
    }

    public <L> WhereSegment eq(LambdaGetter<L> column, Object value) {
        return this.eq(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment eqInSql(String col, String subSql, Object... param){
        this.splitCompareSql(col, SqlOperator.IN, subSql, param);
        return this;
    }

    public <L> WhereSegment eqInSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.eqInSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment ne(String col, Object value){
        this.compareSplit(col, value, SqlOperator.NE);
        return this;
    }

    public <L> WhereSegment ne(LambdaGetter<L> column, Object value) {
        return this.ne(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment and(){
        this.whereConditions.add(new WhereCondition(SqlOperator.AND.getValue()));
        return this;
    }

    public WhereSegment and(WhereLambda lambda){
        this.and();
        this.splitBracket(lambda);
        return this;
    }

    public WhereSegment or(){
        this.whereConditions.add(new WhereCondition(SqlOperator.OR.getValue()));
        return this;
    }

    public WhereSegment or(WhereLambda lambda){
        this.or();
        this.splitBracket(lambda);
        return this;
    }

    public WhereSegment gt(String colum, Object value){
        this.compareSplit(colum,value,SqlOperator.GT);
        return this;
    }

    public <L> WhereSegment gt(LambdaGetter<L> column, Object value) {
        return this.gt(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment ge(String colum, Object value){
        this.compareSplit(colum,value,SqlOperator.GE);
        return this;
    }

    public <L> WhereSegment ge(LambdaGetter<L> column, Object value) {
        return this.ge(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment lt(String colum, Object value){
        this.compareSplit(colum,value,SqlOperator.LT);
        return this;
    }

    public <L> WhereSegment lt(LambdaGetter<L> column, Object value) {
        return this.lt(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment le(String colum, Object value){
        this.compareSplit(colum,value,SqlOperator.LE);
        return this;
    }

    public <L> WhereSegment le(LambdaGetter<L> column, Object value) {
        return this.le(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment gtInSql(String colum, String subSql, Object... param){
        this.splitCompareSql(colum, SqlOperator.GT,"("+subSql+")", param);
        return this;
    }

    public <L> WhereSegment gtInSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.gtInSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment geInSql(String colum, String subSql, Object... param){
        this.splitCompareSql(colum, SqlOperator.GE,"("+subSql+")", param);
        return this;
    }

    public <L> WhereSegment geInSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.geInSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment ltInSql(String colum, String subSql, Object... param){
        this.splitCompareSql(colum, SqlOperator.LT, "("+subSql+")", param);
        return this;
    }

    public <L> WhereSegment ltInSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.ltInSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment leInSql(String colum, String subSql, Object... param){
        this.splitCompareSql(colum, SqlOperator.LE,"("+subSql+")", param);
        return this;
    }

    public <L> WhereSegment leInSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.leInSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment inSql(String colum, String sql, Object... param){
        this.splitCompareSql(colum, SqlOperator.IN, "("+sql+")", param);
        return this;
    }

    public <L> WhereSegment inSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.inSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment notInSql(String colum, String sql, Object... param){
        this.splitCompareSql(colum, SqlOperator.NOT_IN, "("+sql+")", param);
        return this;
    }

    public <L> WhereSegment notInSql(LambdaGetter<L> column, String subSql, Object... param) {
        return this.notInSql(LambdaUtil.getColumnName(column), subSql, param);
    }

    public WhereSegment between(String colum, Object one, Object two){
        this.splitBetween(colum, SqlOperator.BETWEEN, one, two);
        return this;
    }

    public <L> WhereSegment between(LambdaGetter<L> column, Object one, Object two) {
        return this.between(LambdaUtil.getColumnName(column), one, two);
    }

    public WhereSegment notBetween(String colum, Object one, Object two){
        this.splitBetween(colum, SqlOperator.NOT_BETWEEN, one, two);
        return this;
    }

    public <L> WhereSegment notBetween(LambdaGetter<L> column, Object one, Object two) {
        return this.notBetween(LambdaUtil.getColumnName(column), one, two);
    }

    public WhereSegment notIn(String colum, List<?> values){
        this.splitInValues(colum, SqlOperator.NOT_IN, values);
        return this;
    }

    public <L> WhereSegment notIn(LambdaGetter<L> colum, List<?> values){
        return this.notIn(LambdaUtil.getColumnName(colum), values);
    }

    public WhereSegment in(String colum, List<?> values){
        this.splitInValues(colum, SqlOperator.IN, values);
        return this;
    }

    public <L> WhereSegment in(LambdaGetter<L> colum, List<?> values){
        return this.in(LambdaUtil.getColumnName(colum), values);
    }

    public WhereSegment like(String colum , String value){
       this.splitLike(colum, SqlOperator.LIKE, value);
       return this;
    }

    public WhereSegment notLike(String colum , String value){
        this.splitLike(colum, SqlOperator.NOT_LIKE, value);
        return this;
    }

    public <L> WhereSegment notLike(LambdaGetter<L> colum , String value){
        this.notLike(LambdaUtil.getColumnName(colum), value);
        return this;
    }

    public <L> WhereSegment like(LambdaGetter<L> column, String value) {
        return this.like(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment leftLike(String colum , String value){
        this.splitLike(colum, SqlOperator.LIKE_LEFT, value);
        return this;
    }

    public <L> WhereSegment leftLike(LambdaGetter<L> column, String value) {
        return this.leftLike(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment notLeftLike(String colum , String value){
        this.splitLike(colum, SqlOperator.NOT_LIKE_LEFT, value);
        return this;
    }

    public <L> WhereSegment notLeftLike(LambdaGetter<L> colum , String value){
        this.notLeftLike(LambdaUtil.getColumnName(colum), value);
        return this;
    }

    public WhereSegment rightLike(String colum , String value){
        this.splitLike(colum, SqlOperator.LIKE_RIGHT, value);
        return this;
    }

    public <L> WhereSegment rightLike(LambdaGetter<L> column, String value) {
        return this.rightLike(LambdaUtil.getColumnName(column), value);
    }

    public WhereSegment notRightLike(String colum , String value){
        this.splitLike(colum, SqlOperator.NOT_LIKE_RIGHT, value);
        return this;
    }

    public <L> WhereSegment notRightLike(LambdaGetter<L> colum , String value){
        this.notRightLike(LambdaUtil.getColumnName(colum), value);
        return this;
    }

    public WhereSegment isNull(String colum){
        String condition = String.format("%s %s", colum, SqlOperator.IS_NULL.getValue());
        this.whereConditions.add(new WhereCondition(condition));
        return this;
    }

    public <L> WhereSegment isNull(LambdaGetter<L> column) {
        return this.isNull(LambdaUtil.getColumnName(column));
    }

    public WhereSegment isNotNull(String colum){
        String condition = String.format("%s %s", colum, SqlOperator.IS_NOT_NULL.getValue());
        this.whereConditions.add(new WhereCondition(condition));
        return this;
    }

    public <L> WhereSegment isNotNull(LambdaGetter<L> column) {
        return this.isNotNull(LambdaUtil.getColumnName(column));
    }

    public WhereSegment exists(String sql, Object... param) {
        this.splitExists(SqlOperator.EXISTS, sql, param);
        return this;
    }

    public WhereSegment notExists(String sql, Object... param) {
        this.splitExists(SqlOperator.NOT_EXISTS, sql, param);
        return this;
    }


    /**
     * 表达式节点封装
     */
    private static class WhereCondition {
        private final String condition;
        private Object param;
        private final boolean isParam;

        public WhereCondition(String condition, Object param) {
            this.condition = condition;
            this.isParam = true;
            this.param = param;
        }

        public WhereCondition(String condition) {
            this.isParam = false;
            this.condition = condition;
        }

        public String getCondition() {
            return this.condition;
        }

        public Object getParam() {
            return this.param;
        }

        public boolean isParam() {
            return this.isParam;
        }
    }
}
